﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TiposComuns.Objectos;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using TiposComuns.Excepcoes;
using System.Collections;
using System.Runtime.Serialization.Formatters;
using Servidor.Objectos;
using System.Runtime.Remoting.Messaging;
using Servidor.Objectos.Pesquisa;
using Servidor.Objectos.Actions;
using System.Threading;
using Servidor.Objectos.RemoteActions;

namespace Servidor.WebServices
{
    #region Delegates
    delegate void SearchInterestDelegate(string interest);

    delegate void SearchAgeSexDelegate(int ageMin,int ageMax,bool sex);

    delegate void SearchUsernameDelegate(string username);

    delegate void ProcessProfileDelegate(List<int> ids);

    public delegate void ActionDelegate(string msg);

    delegate void RollbackDelegate(int serv,IDMsg id);

    delegate void SetStateDelegate(List<int> rem);

    delegate void GetPeerDelegate(int i, Person person, Peer []peers);
    #endregion

    /// <summary>
    /// Intermediate between the upper level classes of interface or webservices, and the objects
    /// that hold data or allows remote invocations.
    /// </summary>
    public class Proxy
    {
        #region Consts
        public const int DEG_PAR = 3;       //Parallelism degree
        #endregion

        #region Singleton
        private static readonly Proxy instance = new Proxy();
        public static Proxy GetInstance()
        {
            return instance;
        }
        #endregion

        #region Attributes
        private PadiBook padi = new PadiBook();          //Object with the internal state
        private Peer[] servers = new Peer[3];        //Remote servers allocated to the same user
        private Server server;
        private List<Person> results = new List<Person>();  //Results from the last search
        private bool searching = false;                     //If any search is beeing conducted
        private bool primary = false;                       //If i am the primary server
        private event ActionDelegate actionDelegate;
        private Timer pendentTimer;                         /* Timer to execute back-ground operations
                                                             * like executing pending actions
                                                             */
        private Timer searchTimer;                          /*
                                                             * Timer to republish searching information,
                                                             * from time to time
                                                             */
        private Timer checkViewTimer;                       /* Periodic heartbeat of the state                                                             * 
                                                             */
        private TcpChannel tcp;

        private volatile bool running;
        private volatile bool freeze = false;
        private volatile int fdelay = 0;

        public event ActionDelegate ActionDelegate
        {
            add { this.actionDelegate += value; }
            remove
            {
                if (actionDelegate != null)
                {
                    actionDelegate -= value;
                }
            }
        }
        #endregion

        #region Methods

        #region Meta
        public State ChangeServer()
        {
            this.Synchronize(this.padi.State);
            List<int> servs = new List<int>();
            servs.Add(this.padi.ID);
            this.padi.State.View.ViewState.Remove(this.padi.ID);
            foreach (int s in this.padi.State.View.ViewState)
            {
                servs.Add(s);
            }
            this.padi.State.View.ViewState = servs;
            for (int i = 1; i < servs.Count; i++)
            {
                int s = servs[i];
                try
                {
                    servers[s].SetState(this.padi.State);
                }
                catch (Exception)
                {
                    servs.RemoveAt(s);
                    i--;
                }
            }

            string str = "O cliente mudou para a view ";
            foreach(int serv in this.padi.State.View.ViewState)
            {
                str += ">" + serv;
            }
            Notifica(str);
            this.primary = true;

            return this.padi.State;
        }

        public void RollBack(IDMsg id)
        {
            if (freeze)
            {
                Notifica("Estou freezed durante " + fdelay + " s");
                Thread.Sleep(fdelay * 1000);
            }
            this.padi.RollBack(id);
            this.padi.State.IdMensagem = id.Clone();
            this.padi.Save();
            Notifica(("Fiz rollback da msg "+id.ID));

            TimerCallback tc = new TimerCallback(TickCheckView);
            checkViewTimer = new Timer(tc, null, 2000, 6000);
        }

        public void ExitServer(int id)
        {
            Notifica(String.Format("{0} quer sair",id));
            State st = this.padi.State;
            st.View.ViewState.Remove(id);
            st.View.ViewState.Sort();
            this.primary = (st.View.ViewState[0] == this.padi.ID);
        }

        private void Synchronize(State initState)
        {
            int c = 0;                                 //contador para saber se os outros 2 servidores estão down

            for (int i = 0; i < 3; i++)
            {
                if (i != this.padi.ID)
                {
                    /* Inicia o array server com os endereços dos outros servidores */
                    if (servers[i] == null)
                    {
                        try
                        {
                            string address = String.Format("{0}:{1}/Peer",this.padi.User.Person.Address.URI,this.padi.User.Person.Address.Port+i+1);
                            servers[i] = (Peer)Activator.GetObject(typeof(Peer),address);
                        }
                        catch (Exception)
                        {
                            servers[i] = null;
                        }
                    }
                    
                    try
                    {
                        Synchronization sync = servers[i].CheckView(initState,this.padi.ID);
                        if(sync.Actions.Count > 0)//if (sync.State.IdMensagem.CompareTo(this.padi.State.IdMensagem) > 0)
                        {
                            Notifica("Estou desactualizado, tenho de correr as acções.");
                            this.padi.Synchronize(sync.Actions);
                        }
                        if (sync.State.View.VID > this.padi.State.View.VID || sync.State.View.VID == 0)
                        {
                            Notifica("Vou actualizar a vista");
                            this.padi.State = sync.State;
                        }
                    }
                    catch (Exception)
                    {
                        c++;
                        Notifica(String.Format("O {0} está down.",i+1));
                    }
                }
            }

            /* está sozinho e tem de criar nova vista */
            if (c == 2)
            {
                this.padi.State.View.VID++;
                List<int> initview = new List<int>(3);
                initview.Add(this.padi.ID);
                this.padi.State.View.ViewState = initview;
                Notifica("Estou sozinho, e vou criar uma nova vista");
            }

            if (this.padi.State.View.ViewState[0] == this.padi.ID)
            {
                primary = true;
                Notifica("Sou primário");
            }
            padi.Save();
        }

        public bool isAlive()
        {
            Notifica("isAlive!");
            return true;
        }

        public Peer GetPeer(Person person)
        {
            Peer peer = null;
            bool isOK = false;
            int max = 3;
            int[] servs = new int[3];
            Random rand = new Random();
            List<int> svs = new List<int>();
            svs.Add(0);
            svs.Add(1);
            svs.Add(2);
            for (int i = 0; i < 3; i++, max--)
            {
                int r = rand.Next(max);
                servs[i] = svs.ElementAt(r);
                svs.RemoveAt(r);
            }

            Notifica("GetPeer "+person.Username+" ->Comecei");
            for (int i = 0; i < 3 && !isOK; i++)
            {
                try
                {
                    peer = Activator.GetObject(typeof(Peer), person.Address.GetURI(servs[i]) + "/Peer") as Peer;                    
                    isOK = peer.isAlive();
                    Notifica(String.Format("GetPeer->{0} está up",servs[i]+1));
                }
                catch (Exception e)
                {
                    peer = null;
                    Notifica(String.Format("GetPeer->{0} está down ("+e.Message+")",servs[i]+1));
                }
            }

            return peer;
        }

        /*private void GetPeerTH(int i, Person person, Peer []peer)
        {
            
            try
            {
                Peer p = Activator.GetObject(typeof(Peer), person.Address.GetURI(i) + "/Peer") as Peer;
                State s = peer[i].GetState();
                Monitor.Enter(server);
                peer[i] = p;
                Monitor.PulseAll(server);
                Monitor.Exit(server);
                Notifica(String.Format("GetPeer->{0} está up", i + 1));
            }
            catch (Exception)
            {
                Notifica(String.Format("GetPeer->{0} está down", i + 1));
            }
        }

        public Peer GetPeer(Person person)
        {
            Peer[] peer = new Peer[3];
            Timer getPeerTimer = new Timer(GetPeerTick, null, Config.TIMEOUT, System.Threading.Timeout.Infinite);
            Monitor.Enter(server);
            Peer res = null;
            
            for (int i = 0; i < 3; i++)
            {
                GetPeerDelegate del = new GetPeerDelegate(GetPeerTH);
                del.BeginInvoke(i, person, peer, null, null);
            }

            bool f = Monitor.Wait(server);
            for (int i = 0; i < 3 && (res == null); i++)
                res = peer[i];
            Monitor.Exit(server);

            return res;
        }*/

        private void GetPeerTick(object obj)
        {
            Monitor.Enter(server);
            Monitor.PulseAll(server);
            Monitor.Exit(server);
        }

        /// <summary>
        /// Initializes the server
        /// </summary>
        /// <param name="id"></param>
        /// <param name="person"></param>
        /// <returns></returns>
        public State Init(int id, Person person)
        {
            try
            {
                this.padi = PadiBook.Load(person, id);

                //RemotingConfiguration.Configure("App.config", true);

                BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
                provider.TypeFilterLevel = TypeFilterLevel.Full;
                IDictionary props = new Hashtable();
                props["port"] = person.Address.Port + id + 1;
                props["timeout"] = Config.TIMEOUT;

                tcp = new TcpChannel(props, null, provider);
                
                ChannelServices.RegisterChannel(tcp, false);

                server = new Server();
                RemotingServices.Marshal(server, "Server", typeof(IServer));

                servers[id] = new Peer();//new Peer(this);
                RemotingServices.Marshal(servers[id], "Peer", typeof(Peer));

                State state = new State();
                state.IdMensagem = this.padi.State.IdMensagem;
                state.IdPendentes = this.padi.State.IdPendentes;
                this.padi.State = state;
                this.Synchronize(state);              

                //this.InitTimer();

                this.running = true;
                return this.padi.State;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// Connects a user to a primary server
        /// </summary>
        /// <param name="username">Username</param>
        /// <returns>Updated state of the system</returns>
        public State Connect(string username)
        {
            Notifica(username + " conectou-se");
            padi.Connect(username);
            State state = padi.State;

            this.padi.State.View.VID++;
            List<int> list = new List<int>();
            list.Add(padi.ID);
            foreach (int s in this.padi.State.View.ViewState)
            {
                if (s != padi.ID)
                {
                    list.Add(s);
                }
            }
            state.View.ViewState = list;

            if (!this.primary)
            {
                this.primary = true;
                padi.State = state;
                this.UpdateReplicasState();
            }

            Thread th = new Thread(InitSearch);
            th.Start();

            return state;
        }

        public void UpdateReplicasState()
        {
            for (int i = 0; i < servers.Length;i++ )
            {
                if (i != padi.ID && servers[i] != null)
                {
                    try
                    {
                        servers[i].SetState(this.padi.State);
                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        /// <summary>
        /// Gets the current state
        /// </summary>
        /// <returns>The current state of the system</returns>
        public State GetState()
        {
            Notifica("GetState");
            return padi.State;
        }

        /// <summary>
        /// Refreshes the view of the system
        /// </summary>
        /// <returns>Updated state</returns>
        public void SetState(State s)
        {
            padi.State = s;
            primary = (s.View.ViewState[0] == padi.ID);
            padi.Save();
        }

        public void SetView(View view)
        {
            this.padi.State.View = view;
            primary = (view.ViewState[0] == padi.ID);
        }

        private Synchronization GetSynchronization(Servidor.Objectos.Action act)
        {
            Synchronization sync = new Synchronization();
            sync.State = this.padi.State.Clone();            
            sync.State.IdMensagem.isClient = act.Id.isClient;
            //act.Id.ID = sync.State.IdMensagem.ID -1;
            sync.Actions.Add(act);            

            return sync;
        }

        private Synchronization GetSynchronization(List<Servidor.Objectos.Action> acts)
        {
            Synchronization sync = new Synchronization();
            
            sync.State = this.padi.State.Clone();
            foreach (Servidor.Objectos.Action act in acts)
            {
                sync.State.IdMensagem.isClient = act.Id.isClient;
                //act.Id.ID = sync.State.IdMensagem.ID -1;
                sync.Actions.Add(act);
                sync.State.IdMensagem.Inc();
            }

            sync.State.IdMensagem.Dec();

            return sync;
        }

        //actualizar as replicas com uma acçao
        public void UpdateReplicas(Servidor.Objectos.Action act)
        {
            Synchronization sync = GetSynchronization(act);
            
            Notifica(String.Format("UpdateReplicas Action: idinit={0}",this.padi.State.IdMensagem));
            try
            {
                bool ord = this.primary;        //Se já contactei o primário e tenho o id correcto
                bool setState = false;
                List<int> rem = new List<int>();

                State myState;
                lock (this.padi)
                {
                    myState = this.padi.State.Clone();
                }

                for (int i = 0; i < myState.View.ViewState.Count; i++)
                {
                    int serv = myState.View.ViewState[i];
                    if (serv != this.padi.ID)
                    {
                        try
                        {
                            Notifica(String.Format("UpdateReplicas Action: Vou-me ligar à replica {0}", myState.View.ViewState[i] + 1));
                            Synchronization temp = servers[myState.View.ViewState[i]].Update(this.padi.ID,sync);
                            if (!ord)
                            {
                                act.Id = temp.State.IdMensagem.Clone();
                                act.DecID();
                                
                                ord = true;
                            }
                            if (temp.State.IdMensagem.ID>myState.IdMensagem.ID+1)
                            {
                                this.padi.Synchronize(temp.Actions);
                            }

                            if (temp.State.View.VID > myState.View.VID)
                            {
                                temp.State.View.ViewState.Add(this.padi.ID);

                                lock (this.padi)
                                {
                                    this.padi.State = temp.State.Clone();
                                    this.padi.State.IdMensagem.ID--;
                                    myState = this.padi.State.Clone();
                                }
                                setState = true;
                            }

                            Notifica(String.Format("UpdateReplicas Action: Liguei-me à replica {0}", padi.State.View.ViewState[i] + 1));
                        }
                        catch (Exception e)
                        {
                            RollbackDelegate del = new RollbackDelegate(MakeRollback);
                            del.BeginInvoke(serv, act.Id, null, null);

                            Notifica(String.Format("Réplica {0} falhou", serv + 1));

                            if (this.primary)
                            {
                                lock (this.padi)
                                {
                                    padi.State.View.ViewState.RemoveAt(i);
                                    myState.View.ViewState.RemoveAt(i);
                                }
                                i--;                                
                                setState = true;
                            }
                            else
                            {
                                setState = true;
                                rem.Add(serv);
                            }
                        }
                    }
                }

                if (setState)
                {
                    lock (this.padi)
                    {
                        padi.State.View.VID++;
                    }
                    SetStateDelegate del = new SetStateDelegate(MakeSetState);
                    del.BeginInvoke(rem, null, null);
                }
            }
            catch (Exception e)
            {
                throw e;
                //Notifica(String.Format("UpdateReplicas Action: Excepção {0}", e.Message));
            }
            
        }

        public void MakeSetState(List<int> rem)
        {
            try
            {
                View view;
                lock (this.padi)
                {
                    foreach (int i in rem)
                    {
                        this.padi.State.View.ViewState.Remove(i);
                    }
                    view = this.padi.State.View.Clone();
                }

                foreach (int s in view.ViewState)
                {
                    if (s != this.padi.ID)
                    {
                        servers[s].SetView(view);
                        this.primary = (view.ViewState[0] == this.padi.ID);
                    }
                }
            }
            catch(Exception)
            {
            }
        }

        public void MakeRollback(int serv,IDMsg id)
        {
            try
            {
                servers[serv].RollBack(id);
            }
            catch (Exception)
            {
            }
        }

        //actualizar as replicas com varias acçoes
        public void UpdateReplicas(List<Servidor.Objectos.Action> acts)
        {
            Synchronization sync = GetSynchronization(acts);

            Notifica(String.Format("UpdateReplicas Action: idinit={0}", this.padi.State.IdMensagem));
            State myState;
            lock (this.padi.State)
            {
                myState = this.padi.State.Clone();
            }
            try
            {
                
                bool ord = this.primary;        //Se já contactei o primário e tenho o id correcto
                bool setState = false;
                List<int> rem = new List<int>();

                for (int i = 0; i < myState.View.ViewState.Count; i++)
                {
                    int serv = myState.View.ViewState[i];
                    if (serv != this.padi.ID)
                    {
                        try
                        {
                            Notifica(String.Format("UpdateReplicas Action: Vou-me ligar à replica {0}", myState.View.ViewState[i] + 1));
                            Synchronization temp = servers[myState.View.ViewState[i]].Update(this.padi.ID, sync);
                            if (!ord)
                            {
                                int size = sync.Actions.Count;
                                for (int it = size-1; it >= 0;it--)
                                {
                                    Servidor.Objectos.Action act = sync.Actions[it];
                                    act.Id = temp.State.IdMensagem.Clone();
                                    act.Id.ID -= size - it;
                                }
                                ord = true;
                            }

                            if (temp.State.IdMensagem.ID > myState.IdMensagem.ID + acts.Count)
                            {
                                this.padi.Synchronize(temp.Actions);
                            }

                            if (temp.State.View.VID > myState.View.VID)
                            {
                                temp.State.View.ViewState.Add(this.padi.ID);
                                lock (this.padi)
                                {
                                    this.padi.State = temp.State.Clone();
                                    this.padi.State.IdMensagem.ID -= acts.Count;
                                    myState = this.padi.State.Clone();
                                }
                                setState = true;
                            }

                            Notifica(String.Format("UpdateReplicas Action: Liguei-me à replica {0}", padi.State.View.ViewState[i] + 1));
                            
                        }
                        catch (Exception)
                        {
                            RollbackDelegate del = new RollbackDelegate(MakeRollback);
                            del.BeginInvoke(serv, acts[0].Id, null, null);

                            Notifica(String.Format("Réplica {0} falhou", serv + 1));

                            if (this.primary)
                            {
                                lock (this.padi)
                                {
                                    padi.State.View.ViewState.RemoveAt(i);
                                    myState.View.ViewState.RemoveAt(i);
                                }
                                i--;                                
                                setState = true;
                            }
                            else
                            {                                
                                setState = true;
                                rem.Add(serv);
                            }
                        }
                    }
                }

                if (setState)
                {
                    lock (this.padi)
                    {
                        padi.State.View.VID++;
                    }
                    SetStateDelegate del = new SetStateDelegate(MakeSetState);
                    del.BeginInvoke(rem, null, null);
                }
            }
            catch (Exception e)
            {
                throw e;
                //Notifica(String.Format("UpdateReplicas Action: Excepção {0}", e.Message));
            }
        }

        //quando uma replica recebe um update
        public Synchronization Update(int serv,Synchronization sync)
        {
            if (freeze)
            {
                Notifica("Estou freezed durante "+fdelay+" s");
                Thread.Sleep(fdelay*1000);
            }
            Notifica(String.Format("Update: Recebi um update, idmeu={0} idsync={1}", 
                this.padi.State.IdMensagem,sync.State.IdMensagem));
            List<IDMsg> ids = new List<IDMsg>();
            State myState;
            if (this.primary)
            {
                lock (this.padi)
                {                    
                    foreach (Servidor.Objectos.Action action in sync.Actions)
                    {
                        padi.State.IdMensagem.isClient = false;
                        action.Id = padi.State.IdMensagem.Clone();
                        padi.State.IdMensagem.Inc();
                    }
                    myState = this.padi.State.Clone();
                }

                padi.Synchronize(sync.Actions);
                if (sync.State.IdMensagem.ID < myState.IdMensagem.ID)
                {
                    Notifica("Réplica que enviou o update está desactualizada");
                    sync.Actions.AddRange(padi.GetActionsSince(sync.State.IdMensagem));
                }

                sync.State = myState;
                Notifica(String.Format("Update: Sou o primário e vou enviar o id={0}", sync.State.IdMensagem));
            }
            else
            {
                bool b = false;
                lock (this.padi)
                {
                    myState = this.padi.State.Clone();
                }

                if (sync.State.IdMensagem.ID < myState.IdMensagem.ID)
                {
                    Notifica("Réplica que enviou o update está desactualizada");
                    sync.Actions.AddRange(padi.GetActionsSince(sync.State.IdMensagem));
                    b = true;
                }

                lock (this.padi)
                {
                    foreach (Servidor.Objectos.Action action in sync.Actions)
                    {
                        if (b)
                        {
                            action.Id = this.padi.State.IdMensagem.Clone();
                            this.padi.State.IdMensagem.Inc();
                        }
                        if (action.GetType().Equals(typeof(UpdateProfileAction)))
                        {
                            PendingProfile(((UpdateProfileAction)action).Profile, this.padi.User.Perfil);
                        }
                    }
                }


                padi.Synchronize(sync.Actions);
                lock (this.padi)
                {
                    padi.State.IdMensagem = sync.State.IdMensagem;
                    padi.State.View = sync.State.View;                    
                }

                Notifica(
                    String.Format(
                    "Update: Não sou o primário e respondo com o id {0}",
                    sync.State.IdMensagem));
            }
                        
            padi.Save();

            return sync;
        }

        public void Notifica(string message)
        {
            if (actionDelegate != null)
            {
                this.actionDelegate(message);
            }
        }

        public Synchronization CheckView(State state, int id)
        {
            State mystate = this.GetState();

            /* se a VID for igual e nao estiver no view state, entao acrescenta e
             * actualiza a vista */
            Synchronization res = new Synchronization();
            

            Notifica(String.Format("CheckView: Recebi check do servidor {0} em que o estado é vid={1},idmsg={2}", id + 1, state.View.VID,
                state.IdMensagem));
            res.State = mystate;

            if (state.IdMensagem.ID < mystate.IdMensagem.ID && this.primary)
            {
                /* mudar o estado para sync */
                Notifica("É preciso actualizar as mensagens do " + (id + 1));
                res.Actions = this.GetActionsSince(state.IdMensagem);
            }
            else
            {
                //eu é que estou desactualizado e preciso de pedir as acçoes que me faltam
                if (state.IdMensagem.ID > mystate.IdMensagem.ID)
                {
                    List<Servidor.Objectos.Action> acts = servers[id].GetActionsSince(mystate.IdMensagem);
                    Notifica(String.Format("O {0} está mais actualizado que eu, tenho de correr as acções que me faltam.", id + 1));
                    mystate.IdMensagem = state.IdMensagem;
                    mystate.IdPendentes = state.IdPendentes;
                    this.padi.Synchronize(acts);
                }
            }

            if (state.View.VID < mystate.View.VID || mystate.View.VID == 0)
            {
                Notifica("É preciso actualizar a vista do " + (id + 1));
                mystate.View.AddServer(id);
                mystate.View.Inc();
                this.SetState(mystate);
            }

            if (this.primary && mystate.View.ViewState[0] != this.padi.ID)
            {
                this.Notifica("ja nao sou o primario");
                this.primary = false;
            }

            return res;
        }

        #region Timers
        private void InitTimer()
        {
            /*TimerCallback tc = new TimerCallback(TickPendent);
            pendentTimer = new Timer(tc,null,10000,6000000);

            tc = new TimerCallback(TickSearch);
            searchTimer = new Timer(tc,null,1000000,36000000);

            tc = new TimerCallback(TickCheckView);
            checkViewTimer = new Timer(tc,null,3000,30000);*/
        }

        private void TickCheckView(object obj)
        {
            Notifica("TickCheck");
            this.Synchronize(this.padi.State.Clone());
            checkViewTimer.Dispose();
        }        

        private void TickPendent(object obj)
        {
            if (this.running)
            {
                Notifica("TickPendent");
                if (this.primary)
                {
                    lock (this.padi.Pendents)
                    {
                        List<int> ids = new List<int>();
                        foreach (RemoteAction ra in this.padi.Pendents.Actions.Values)
                        {
                            if (ra.Execute())
                            {
                                this.padi.Pendents.Remove(ra.ID);
                                ids.Add(ra.ID);
                            }
                        }
                        if (ids.Count > 0)
                        {
                            this.RemovePendents(ids);
                            this.padi.Save();
                        }
                    }
                }
            }            
        }

        private void TickSearch(object obj)
        {
            if (this.running)
            {
                Notifica("TickSearch");
                SerializableDictionary<int,SearchResult>.ValueCollection keys;
                lock (this.padi.User.Searched)
                {
                    keys = this.padi.User.Searched.Map.Values;
                }
                foreach (SearchResult sr in keys)
                {
                    byte[] key = sr.Key;
                    KClosest closest = this.GetClosest(key);

                    bool me = false;
                    foreach (Person person in closest.Nodes.Array)
                    {
                        if (person!=null&&!person.Username.Equals(this.padi.User.Person.Username))
                        {
                            Peer peer = this.GetPeer(person);
                            if (peer != null)
                            {
                                peer.Store(key, this.padi.User.Searched.Get(key));
                            }
                        }
                        else me = true;
                    }

                    if (!me)
                    {
                        this.padi.User.Searched.Remove(key);
                    }
                }
            }
        }

        private void EndTimers()
        {
            pendentTimer.Dispose();
            searchTimer.Dispose();
            //checkViewTimer.Dispose();
        }

        private void RemovePendents(List<int> pends)
        {
            foreach (int s in this.padi.State.View.ViewState)
            {
                if (s != this.padi.ID)
                {
                    servers[s].RemovePendents(pends);
                }
            }
        }

        public void RemoveLocalPendents(List<int> pends)
        {
            foreach (int i in pends)
            {
                this.padi.Pendents.Actions.Remove(i);
            }
            this.padi.Save();
        }
        #endregion

        #region Exit
        private void OrderedExit()
        {
            foreach (int i in this.padi.State.View.ViewState)
            {
                if (i != this.padi.ID)
                {
                    this.servers[i].ServerExit(this.padi.ID);
                }
            }
        }

        public void Desliga()
        {
            this.OrderedExit();
            this.running = false;
            this.EndTimers();
        }
        #endregion

        #region Freeze
        public void FreezeStart(int delay)
        {
            fdelay = delay;
            freeze = true;
        }

        public void FreezeStop()
        {            
            this.freeze = false;
            this.fdelay = 0;
        }
        #endregion

        #endregion

        #region Profile
        /// <summary>
        /// Returns the user's profile
        /// </summary>
        /// <returns>User's profile</returns>
        public Profile GetProfile()
        {
            return padi.User.Perfil;
        }

        private List<int> PendingProfile(Profile novo, Profile old)
        {
            List<int> list = new List<int>();
            if ((old.Idade == 0 && novo.Idade!=0) || ((int)(novo.Idade / 10)) != ((int)(old.Idade / 10)) || old.Genero!=novo.Genero)
            {
                int id = this.padi.State.IdPendentes;
                               

                if (old.Idade != 0)
                {
                    id = this.padi.State.IdPendentes;
                    this.padi.State.IdPendentes++;

                    this.padi.Pendents.Add(
                        new RemoteRemoveSearchedAction(id,
                            HashService.CalculateHash(old.Idade, old.Genero),
                            this.padi.User.Person));
                    list.Add(id);
                }

                id = this.padi.State.IdPendentes;
                this.padi.State.IdPendentes++;
                this.padi.Pendents.Add(
                    new RemoteAddSearchedAction(id,
                        HashService.CalculateHash(novo.Idade, novo.Genero),
                        this.padi.User.Person));
                list.Add(id);
            }
            foreach (string inter in old.Interesses)
            {
                if (!novo.Interesses.Contains(inter))
                {
                    int id = this.padi.State.IdPendentes;
                    this.padi.State.IdPendentes++;

                    this.padi.Pendents.Add(
                        new RemoteRemoveSearchedAction(id,
                            HashService.CalculateHash(inter),
                            this.padi.User.Person));
                    list.Add(id);
                }
            }
            foreach (string inter in novo.Interesses)
            {
                if (!old.Interesses.Contains(inter))
                {
                    int id = this.padi.State.IdPendentes;
                    this.padi.State.IdPendentes++;

                    this.padi.Pendents.Add(
                        new RemoteAddSearchedAction(id,
                            HashService.CalculateHash(inter),
                            this.padi.User.Person));
                    list.Add(id);
                }
            }

            return list;
        }

        private void ProcessProfile(List<int> process)
        {

            List<int> list = new List<int>();
            foreach (int id in process)
            {
                if (this.padi.Pendents.Get(id).Execute())
                {
                    this.padi.Pendents.Remove(id);
                    list.Add(id);
                }
            }

            if (list.Count > 0)
            {
                this.RemovePendents(list);
                this.padi.Save();
            }
        }


        public bool ProcessID(IDMsg id)
        {
            bool res;
            lock (this.padi)
            {
                if (this.primary)
                {
                    id.ID = this.padi.State.IdMensagem.ID;
                    this.padi.State.IdMensagem.Inc();
                    return true;
                }

                this.primary = true;

                State state = this.padi.State;
                state.View.ViewState.RemoveAt(0);

                int aux;
                state.View.ViewState.Remove(this.padi.ID);
                if (state.View.ViewState.Count > 0)
                {
                    aux = state.View.ViewState[0];
                    state.View.ViewState.RemoveAt(0);
                    state.View.ViewState.Add(this.padi.ID);
                    state.View.ViewState.Add(aux);
                }
                else state.View.ViewState.Add(this.padi.ID);

                IDMsg idcl = this.padi.LogFile.GetLastClient();

                res = idcl.ID < id.ID;

                id.ID = this.padi.State.IdMensagem.ID;
                this.padi.State.View.VID++;
                state.IdMensagem.isClient = true;
                this.padi.State.IdMensagem.Inc();
            }

            return res;
        }


        /// <summary>
        /// Sets the user's profile
        /// </summary>
        /// <returns>New profile</returns>
        public State SetProfile(IDMsg id, Profile profile)
        {
            bool flag = ProcessID(id);
            Notifica(String.Format("Vou alterar o perfil {0}", id));

            
            this.UpdateReplicas(new UpdateProfileAction(id, profile, this.padi.User.Perfil));

            State temp = this.padi.State;
            if (temp.IsOK() && flag)
            {
                List<int> ids = PendingProfile(profile, this.padi.User.Perfil);
                this.padi.SetProfile(profile);
                //this.padi.State.IdMensagem.Inc();
                this.padi.Save();
                Notifica(String.Format("Alterei o perfil {0}", id));

                ProcessProfileDelegate del = new ProcessProfileDelegate(ProcessProfile);
                del.BeginInvoke(ids, null, null);
            }
            else if (!temp.IsOK())
            {
                lock (this.padi)
                {
                    this.padi.State.IdMensagem.Dec();
                }
            }

            return this.padi.State;
        }
        
        public Address GetAddress()
        {
            return this.padi.User.Person.Address;
        }
        #endregion        

        #region Posts
        public State Post(Post post)
        {
            bool flag;

            flag = ProcessID(post.ID);
            this.padi.State.IdMensagem.isClient = true;
            post.ID.isClient = true;

            Notifica(String.Format("Vou publicar {0}:{1}", post.ID, post.Message));
            this.UpdateReplicas(new PostAction(post.ID, post));

            State temp;
            lock (this.padi)
            {
                temp = this.padi.State;
                if (temp.IsOK() && flag)
                {
                    padi.Post(post);
                    //this.padi.State.IdMensagem.Inc();
                    this.padi.Save();
                    Notifica(String.Format("Publiquei {0}:{1}", post.ID, post.Message));
                }
                else
                {
                    this.padi.State.IdMensagem.Dec();
                }
            }

            return temp;
        }

        public KeyValuePair<State,List<Post>> RefreshWall()
        {
            Notifica(String.Format("RefreshWall: id={0}",this.padi.State.IdMensagem));
            Dictionary<string, IDMsg> lasts = this.padi.GetLastReceivedPosts();
            List<Post> list = new List<Post>();
            IDMsg id;
            lock (this.padi)
            {
                this.padi.State.IdMensagem.isClient = false;
                id = this.padi.State.IdMensagem.Clone();
            }
            id.isClient = false;
            List<Servidor.Objectos.Action> lactions = new List<Servidor.Objectos.Action>();

            List<Person> friends = new List<Person>();
            lock (this.padi.User.Friends)
            {
                friends.AddRange(this.padi.GetFriends());
            }
            foreach (Person person in friends)
            {
                try
                {
                    Peer peer = GetPeer(person);
                    if (peer != null)
                    {
                        int lt =
                            (lasts.ContainsKey(person.Username)) ? lasts[person.Username].ID : 0;

                        List<Post> lts = peer.PostsSince(this.padi.User.Person.Username, lt);

                        list.AddRange(lts);

                        //actualizar as replicas                    

                        foreach (Post p in lts)
                        {
                            lactions.Add(new PostAction(id.Clone(), p));
                            id.Inc();
                        }                        
                    }
                }
                catch (Exception)
                {
                    this.padi.User.SearchTree.Remove(person.Hash);
                }
            }

            if (lactions.Count > 0)
            {
                lock (this.padi)
                {
                    this.padi.State.IdMensagem.ID += lactions.Count;
                }

                this.UpdateReplicas(lactions);
                State temp = this.padi.State;

                if (temp.IsOK())
                {
                    this.padi.PostAll(list);
                    //this.UpdateState(temp);
                    //this.padi.State.IdMensagem = id.Clone();
                    this.padi.Save();
                    Notifica(String.Format("RefreshWall: Terminei id={0}", this.padi.State.IdMensagem));
                }
                else
                {
                    lock (this.padi)
                    {
                        this.padi.State.IdMensagem.ID -= lactions.Count;
                    }
                }
            }
            return new KeyValuePair<State,List<Post>>(this.padi.State,list);
        }

        public List<Post> PostsSince(string user, int last)
        {
            List<Post> res = new List<Post>();
            bool ReqOrSend = false;
            foreach (Post p in this.padi.PostsSince(last))
            {
                if (p.GetType().Equals(typeof(NewFriendPost)))
                {
                    NewFriendPost temp = ((NewFriendPost)p);
                    ReqOrSend = temp.Accepter.Equals(user) || temp.Requester.Equals(user);
                }
                if (!p.User.Username.Equals(user) && !ReqOrSend && p.User.Username.Equals(this.padi.User.Person.Username))
                    res.Add(p);
            }
            return res;
        }

        public List<Post> GetPosts()
        {
            return this.padi.GetPosts();
        }

        #endregion

        #region Friends
        public List<Person> GetFriends()
        {
            return padi.GetFriends();
        }

        public List<Person> GetFriendsReceived()
        {
            return padi.GetFriendsReceived();
        }

        public List<Person> GetFriendsSent()
        {
            return padi.GetFriendsSent();
        }

        public State FriendRequest(IDMsg id, Person person)
        {
            bool flag = ProcessID(id);
            Notifica(String.Format("FriendRequest: Vou enviar para {0} id={1},{2}", person.Username, id.ID,id.isClient));
            if (!padi.MayAddFriend(person))
            {
                throw new FriendNotAvailableException(person);
            }

            Peer peer = this.GetPeer(person);
            
            if (peer != null && peer.FriendReceive(this.padi.User.Person))
            {
                this.padi.State.IdMensagem.isClient = true;
                this.UpdateReplicas(new RequestFriendAction(id, person));
                State temp = this.padi.State;
                if (temp.IsOK()&&flag)
                {
                    this.padi.FriendRequest(person);
                    //this.padi.State.IdMensagem.Inc();
                    this.padi.User.SearchTree.Add(person);
                    this.padi.Save();
                }
                else if (!temp.IsOK())
                {
                    lock (this.padi)
                    {
                        this.padi.State.IdMensagem.Dec();
                    }
                }
                if (this.padi.User.SearchTree.Add(person))
                {
                    Thread th = new Thread(JoinFriend);
                    th.Start();
                }
                Notifica(String.Format("FriendRequest: Pedido enviado para {0} id={1},{2}", person.Username, id.ID, id.isClient));
            }
            else
            {
                throw new FriendNotAvailableException(person);
            }
            

            return this.padi.State;
        }

        public bool FriendReceive(Person person)
        {
            Notifica(String.Format("FriendReceive: Recebi um friend request de {0}, com id de mensagem {1}", 
                person.Username, this.padi.State.IdMensagem));
            IDMsg id;
            lock (this.padi)
            {
                this.padi.State.IdMensagem.isClient = false;
                id = this.padi.State.IdMensagem.Clone();
                this.padi.State.IdMensagem.Inc();
            }


            this.UpdateReplicas(new ReceiveRequestAction(id, person));
            State temp = this.padi.State;
            
            if (temp.IsOK())
            {
                this.padi.FriendReceive(person);
                
                Notifica(String.Format("FriendReceive: Replicação feita  id={0}", this.padi.State.IdMensagem));
                this.padi.User.SearchTree.Add(person);
                this.padi.Save();
                if (this.padi.User.SearchTree.Add(person))
                {
                    Thread th = new Thread(JoinFriend);
                    th.Start();
                }
                return true;
            }
            else
            {
                lock (this.padi)
                {
                    this.padi.State.IdMensagem.Dec();
                }
                Notifica(String.Format("FriendReceive: Replicação não foi feita id={0} (há menos de 2 servidores)", this.padi.State.IdMensagem));
                return false;
            }
        }
            

        //enviado por quem aceita o friend request
        public State FriendAccept(IDMsg id, Person friend)
        {
            bool flag = ProcessID(id);
            Notifica(String.Format("FriendAccept: Request enviado para {1} id={0}", id, friend.Username));
            Peer peer = this.GetPeer(friend);

            if (this.padi.State.IsOK() && peer != null && peer.BeAccepted(this.padi.User.Person))
            {                
                NewFriendPost nfp = new NewFriendPost();
                nfp.ID = id;
                nfp.Message = String.Format("{0} [{2}] e {1} [{3}] são amigos.", friend.Username, this.padi.User.Person.Username,friend.Address,padi.User.Person.Address);
                nfp.Requester = friend;
                nfp.User = this.padi.User.Person;
                nfp.Date = DateTime.Now;
                nfp.Accepter = this.padi.User.Person;

                this.padi.State.IdMensagem.isClient = true;
                this.UpdateReplicas(new AcceptFriendAction(id, nfp));
                State temp = this.padi.State;
                this.padi.User.SearchTree.Add(friend);
                if (temp.IsOK() && flag)
                {
                    this.padi.FriendAccept(nfp);
                    //this.padi.State.IdMensagem.Inc();
                    this.padi.Save();
                    Notifica(String.Format("FriendAccept: Request bem sucedido id={0}", this.padi.State.IdMensagem));
                }
                else if (!temp.IsOK())
                {
                    lock (this.padi)
                    {
                        this.padi.State.IdMensagem.Dec();
                    }
                }
            }
            else
            {
                Notifica(String.Format("FriendAccept: Request falhou para {1} id={0}", id, friend.Username));
                throw new FriendNotAvailableException(friend);
            }


            return this.padi.State;
        }

        //recebido por quem tinha enviado o friend request
        public bool BeAccepted(Person friend)
        {
            this.padi.State.IdMensagem.isClient = false;
            IDMsg id;
            lock (this.padi)
            {
                this.padi.State.IdMensagem.isClient = false;
                id = this.padi.State.IdMensagem.Clone();                
                this.padi.State.IdMensagem.Inc();
            }
            
            Notifica(String.Format("BeAccepted: Recebi accept de {1} id={0}", id, friend.Username));

            NewFriendPost nfp = new NewFriendPost();
            nfp.ID = id;
            nfp.Message = String.Format("{0} [{2}] e {1} [{3}] são amigos.", friend.Username, this.padi.User.Person.Username, friend.Address, padi.User.Person.Address);
            nfp.Requester = friend;
            nfp.User = friend;
            nfp.Date = DateTime.Now;
            nfp.Accepter = this.padi.User.Person;            
            this.UpdateReplicas(new AcceptFriendAction(id, nfp));
            State temp = this.padi.State;
            if (temp.IsOK())
            {
                this.padi.FriendAccept(nfp);
                //this.padi.State.IdMensagem.Inc();
                this.padi.User.SearchTree.Add(friend);
                this.padi.Save();
                Notifica(String.Format("BeAccepted: Accept replicado id={0}", this.padi.State.IdMensagem));
                return true;
            }
            else
            {
                this.padi.State.IdMensagem.Dec();
                Notifica(String.Format("BeAccepted: Accept falhou nºservidores={0}", this.padi.State.View.ViewState.Count));
                return false;
            }
        }

        //enviado por quem rejeita
        public State FriendReject(IDMsg id, Person person)
        {
            bool flag = ProcessID(id);
            Notifica(String.Format("FriendReject: Vou enviar para {1} id={0}", id, person.Username));
            Peer peer = this.GetPeer(person);
            if (this.padi.State.IsOK() && peer != null && peer.BeRejected(this.padi.User.Person))
            {
                this.padi.State.IdMensagem.isClient = true;
                this.UpdateReplicas(new RejectRequestAction(id, person));
                State temp = this.padi.State;
                if (temp.IsOK()&&flag)
                {
                    this.padi.FriendReject(person);
                    //this.padi.State.IdMensagem.Inc();
                    this.padi.User.SearchTree.Add(person);
                    this.padi.Save();
                    Notifica(String.Format("FriendReject: Reject bem sucedido id={0}", this.padi.State.IdMensagem));
                }
                else if (!temp.IsOK())
                {
                    lock (this.padi)
                    {
                        this.padi.State.IdMensagem.Dec();
                    }
                }
            }
            else
            {
                Notifica(String.Format("FriendReject: Reject falhou para {1} id={0}", id, person.Username));
                throw new FriendNotAvailableException(person);
            }

            return this.padi.State;
        }

        //recebido por quem e rejeitado
        public bool BeRejected(Person person)
        {
            this.padi.State.IdMensagem.isClient = false;
            IDMsg id;
            lock (this.padi)
            {
                this.padi.State.IdMensagem.isClient = false;
                id = this.padi.State.IdMensagem.Clone();
                this.padi.State.IdMensagem.Inc();
            }

            Notifica(String.Format("BeRejected: Recebi reject de {1} id={0}", id, person.Username));
            this.UpdateReplicas(new RejectRequestAction(id, person));
            State temp = this.padi.State;
            if (temp.IsOK())
            {
                Notifica(String.Format("BeRejected: Reject replicado id={0}", this.padi.State.IdMensagem));
                this.padi.FriendReject(person);
                //this.padi.State.IdMensagem.Inc();
                this.padi.User.SearchTree.Add(person);
                this.padi.Save();
                return true;
            }
            else
            {
                this.padi.State.IdMensagem.Dec();
                Notifica(String.Format("BeRejected: Reject falhou nºservidores={0}", this.padi.State.View.ViewState.Count));
                return false;
            }
        }

        public bool BeRemoved(Person person)
        {
            this.padi.State.IdMensagem.isClient = false;
            IDMsg id;
            lock (this.padi)
            {
                this.padi.State.IdMensagem.isClient = false;
                id = this.padi.State.IdMensagem.Clone();
                this.padi.State.IdMensagem.Inc();
            }
            Notifica(String.Format("BeRemoved: Recebi remove de {1} id={0}", id, person.Username));
            this.UpdateReplicas(new RemoveFriendAction(id, person));
            State temp = this.padi.State;

            if (temp.IsOK())
            {
                Notifica(String.Format("BeRemoved: Remove replicado id={0}", this.padi.State.IdMensagem));
                this.padi.FriendRemove(person);
                //this.padi.State.IdMensagem.Inc();
                this.padi.User.SearchTree.Add(person);
                this.padi.Save();
                return true;
            }
            else
            {
                this.padi.State.IdMensagem.Dec();
                Notifica(String.Format("BeRemoved: Remove falhou nºservidores={0}", this.padi.State.View.ViewState.Count));
                return false;
            }
        }

        public State FriendRemove(IDMsg id, Person target)
        {
            bool flag = ProcessID(id);
            Notifica(String.Format("FriendRemove: Vou enviar para {1} id={0}", id, target.Username));
            Peer peer = this.GetPeer(target);
            if (this.padi.State.IsOK() && peer != null && peer.BeRemoved(this.padi.User.Person))
            {
                this.padi.State.IdMensagem.isClient = true;
                this.UpdateReplicas(new RemoveFriendAction(id, target));
                State temp = this.padi.State;
                if (temp.IsOK() && flag)
                {
                    this.padi.FriendRemove(target);
                    this.padi.State.IdMensagem.Inc();
                    this.padi.User.SearchTree.Add(target);
                    this.padi.Save();
                    Notifica(String.Format("FriendRemove: Remove bem sucedido id={0}", this.padi.State.IdMensagem));
                }
                else if (!temp.IsOK())
                {
                    this.padi.State.IdMensagem.Dec();
                }
            }
            else
            {
                Notifica(String.Format("FriendRemove: Remove falhou para {1} id={0}", id, target.Username));
                throw new FriendNotAvailableException(target);
            }

            return this.padi.State;
        }
        #endregion

        #region LogFile
        public List<Servidor.Objectos.Action> GetActionsSince(IDMsg lastid)
        {
            return this.padi.GetActionsSince(lastid);
        }
        #endregion

        #region Search

        #region Protocol
        public void JoinFriend()
        {
            Notifica("Entrei no join friend");
            this.GetClosest(this.padi.User.Person.Hash);
            Dictionary<int, SearchResult> mapa = new Dictionary<int,SearchResult>();
            lock (this.padi.User.Searched)
            {
                foreach (SearchResult sr in this.padi.User.Searched.Map.Values)
                {
                    mapa.Add(this.padi.User.Searched.GetKey(sr.Key), sr);
                }
            }
            Notifica("     Inicio do foreach do mapa.values");
            foreach (SearchResult sr in mapa.Values)
            {
                KClosest closest = this.GetClosest(sr.Key);
                for (int i = 0; i < closest.Nodes.Lenght; i++)
                {
                    Person per = closest.Nodes.Array[i];
                    if (!per.Username.Equals(this.padi.User.Person.Username))
                    {
                        Peer peer = this.GetPeer(per);
                        if (peer != null)
                        {
                            try
                            {
                                peer.Store(sr.Key, sr);
                            }
                            catch (Exception e)
                            {
                                //throw e;
                            }
                        }
                    }
                }
            }
            Notifica("     Fim do foreach do mapa.values");

            this.PropagateTree();
            Notifica("Sai do join friend" + System.Environment.NewLine);
        }

        public SearchResult GetSearched(byte []key)
        {
            return this.padi.User.Searched.Get(key);
        }

        public KClosest GetLocalKClosest(Person person)
        {
            return this.padi.User.SearchTree.GetClosest(person.Hash);            
        }

        public KClosest GetLocalKClosest(byte []hash)
        {
            return this.padi.User.SearchTree.GetClosest(hash);            
        }

        public KClosest GetClosest(Person person)
        {
            return this.GetClosest(person.Hash);
        }

        public KClosest GetClosest(byte [] key)
        {
            Notifica("Entrei no getclosest(byte)");
            KTree tree = this.padi.User.SearchTree;
            KClosest closest = tree.GetClosest(key);

            List<Person> rec = closest.GetRecent(Proxy.DEG_PAR);        /* Retrieves DEG_PAR nodes that weren't yet contacted
                                                                         * DEG_PAR is the degree of parallelism
                                                                         */
            while (rec.Count > 0)
            {
                for (int i = 0; i < rec.Count; i++)
                {
                    if (!rec[i].Username.Equals(this.padi.User.Person.Username) && rec[i]!=null)
                    {
                        try
                        {
                            Peer peer = this.GetPeer(rec[i]);
                            if (peer != null)
                            {
                                Notifica("    Vou adicionar "+rec[i].Username+" à searchtree");
                                closest.AddAll(peer.GetClosest(key));
                                this.padi.User.SearchTree.Add(rec[i]);
                            }
                            else this.padi.User.SearchTree.Remove(rec[i].Hash);
                        }
                        catch (Exception e)
                        {
                            Notifica("Perdi a conexão com o peer no getclosest.("+e.Message+")");
                        }
                    }
                }
                rec = closest.GetRecent(Proxy.DEG_PAR);
            }
            Notifica("Sai do getclosest(byte)"+System.Environment.NewLine);
            return closest;
        }

        #endregion

        #region By Interest, Age, Sex, Username

        #region Callbacks
        private void CallBackSearch(IAsyncResult res)
        {
            /*SearchInterestDelegate del = ((AsyncResult)res).AsyncDelegate as SearchInterestDelegate;
            del.EndInvoke(res);*/

            lock (results)
            {
                this.searching = false;
            }
        }

        #endregion

        #region Searching
        private void SearchBy(byte []hash)
        {
            KClosest closest = this.GetClosest(hash);
            SearchResult sr  =null;
            bool b = false;
            do
            {
                for (int i = 0; i < closest.Nodes.Lenght && !b; i++)
                {
                    Person p = closest.Nodes.Array[i];
                    if (p.Username.Equals(this.padi.User.Person.Username))
                    {
                        sr = this.GetSearched(hash);
                        b = (sr != null);
                    }
                    else
                    {
                        Peer peer = this.GetPeer(closest.Nodes.Array[i]);

                        if (peer != null)
                        {
                            sr = peer.GetSearched(hash);
                            b = (sr != null);
                        }
                    }
                }
                if (b)
                {
                    lock (this.results)
                    {
                        this.results.AddRange(sr.Results);
                    }
                    closest = sr.Nexts;
                }
                else closest = null;
            } while (closest!=null);
        }

        private void SearchBy(byte[] hash,int ageMin,int ageMax)
        {
            KClosest closest = this.GetClosest(hash);
            SearchResult sr = null;
            bool b = false;
            do
            {
                for (int i = 0; i < closest.Nodes.Lenght && !b; i++)
                {
                    Peer peer = this.GetPeer(closest.Nodes.Array[i]);

                    if (peer != null)
                    {
                        try
                        {
                            sr = peer.GetSearched(hash);
                            b = (sr != null);
                        }
                        catch (Exception)
                        {
                            b = false;
                        }
                    }
                }
                if (b)
                {

                    foreach (Person person in sr.Results)
                    {
                        Peer peer = this.GetPeer(person);
                        if (peer != null)
                        {
                            try
                            {
                                Profile profile = peer.GetProfile();
                                if (profile.Idade >= ageMin && profile.Idade <= ageMax)
                                {
                                    lock (this.results)
                                    {
                                        this.results.Add(person);
                                    }
                                }
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                    closest = sr.Nexts;
                }
                else closest = null;
            } while (closest != null);
        }

        private void SearchByInterest(string interest)
        {
            byte[] hash = HashService.CalculateHash(interest);
            this.SearchBy(hash);
        }

        private void SearchByAgeSex(int ageMin,int ageMax,bool sex)
        {
            int ageaux = (ageMin / 10) * 10;
            int agemax = (ageMax / 10) * 10 + 10;
            while (ageaux < agemax)
            {
                byte[] hash = HashService.CalculateHash(ageaux, sex);
                
                if (ageaux >= ageMin && ageaux < agemax - 10)
                {
                    this.SearchBy(hash);
                }
                else
                {
                    this.SearchBy(hash, ageMin, ageMax);
                }
                ageaux += 10;
            }
        }

        public void SearchByUsername(string username)
        {
            Person per = new Person(username, null);
            KClosest close = this.GetClosest(per);


            if (close.Nodes.Lenght > 0 && close.Nodes.Array[0].Username.Equals(username))
            {
                lock (results)
                {
                    this.results.Add(close.Nodes.Array[0]);
                }
            }            
        }
        #endregion

        #region Start

        private void LockSearch()
        {
            lock (results)
            {
                if (searching)
                    throw new AllreadyPerformingASearchException("Já está a decorrer uma pesquisa");

                searching = true;
            }
        }

        public void StartLookupUsername(string username)
        {
            this.LockSearch();

            AsyncCallback ac = new AsyncCallback(CallBackSearch);

            SearchUsernameDelegate del = new SearchUsernameDelegate(SearchByUsername);
            del.BeginInvoke(username, ac, null);
        }

        public void StartLookupInterest(string interest)
        {
            this.LockSearch();

            AsyncCallback ac = new AsyncCallback(CallBackSearch);

            SearchInterestDelegate del = new SearchInterestDelegate(SearchByInterest);
            del.BeginInvoke(interest, ac, null);
        }

        public void StartLookupAgeSex(int ageMin,int ageMax,bool sex)
        {
            this.LockSearch();

            AsyncCallback ac = new AsyncCallback(CallBackSearch);

            SearchAgeSexDelegate del = new SearchAgeSexDelegate(SearchByAgeSex);
            del.BeginInvoke(ageMin,ageMax,sex, ac, null);
        }

        #endregion

        #region Obtain results
        public KeyValuePair<List<Person>,bool> GetResults()
        {
            KeyValuePair<List<Person>, bool> pair;
            List<Person> res = new List<Person>();
            lock (results)
            {
                res.AddRange(this.results);
                this.results.Clear();
                
                pair = new KeyValuePair<List<Person>,bool>(res,searching);
            }
            

            return pair;
        }
        #endregion

        #endregion

        #region Maintenance
        public void SetTree(KTree tree)
        {
            this.padi.User.SearchTree = tree;
        }

        private void PropagateTree()
        {
            List<int> viewState = new List<int>();
            lock (this.padi)
            {
                viewState.AddRange(this.padi.State.View.ViewState);
            }

            for (int i = 0; i < viewState.Count; i++)
            {
                if (viewState[i] != this.padi.ID)
                {
                    try
                    {
                        servers[viewState[i]].SetTree(this.padi.User.SearchTree);
                    }
                    catch (Exception)
                    {
                        lock (this.padi)
                        {
                            Notifica(String.Format("Réplica {0} falhou", viewState[i] + 1));
                            padi.State.View.ViewState.RemoveAt(i);
                            viewState.RemoveAt(i);
                            i--;
                            padi.State.View.VID++;
                        }
                    }
                }
            }
        }

        private void InitSearch()
        {
            this.GetClosest(this.padi.User.Person);
            this.padi.User.SearchTree.Add(this.padi.User.Person);
            this.PropagateTree();
        }

        public KeyValuePair<SearchResult,bool> StoreSearched(byte []key, Person person)
        {
            SearchResult sr;
            SearchResult old = this.padi.User.Searched.Get(key);
            Notifica("Comecei a fazer store da chave " + this.padi.User.Searched.GetKey(key));

            bool f;
            if (old == null)
            {
                sr = new SearchResult(key);
                f = true;
            }
            else
            {
                sr = new SearchResult(old);
                f = !old.IsFull();
            }

            if (!f && !old.HasNexts())
            {
                byte[] contKey = HashService.RandomHash();
                KClosest closest = this.GetLocalKClosest(contKey);

                old.SetNexts(contKey, closest);
                sr.SetNexts(contKey, closest);
            }
            else if (f)
            {
                this.padi.State.IdMensagem.isClient = false;
                IDMsg id;
                lock (this.padi)
                {
                    id = this.padi.State.IdMensagem.Clone();
                    this.padi.State.IdMensagem.Inc();
                }
                
                this.UpdateReplicas(new StoreSearchedAction(id,key,person));
                State temp = this.padi.State;

                if (temp.IsOK())
                {
                    sr.Add(person);
                    //this.padi.State.IdMensagem.Inc();
                    this.padi.User.Searched.Add(key, person);
                    this.padi.LogFile.Register(new StoreSearchedAction(id, key, person));
                    this.padi.Save();
                }
                else
                {
                    lock (this.padi)
                    {
                        this.padi.State.IdMensagem.Dec();
                    }
                    return new KeyValuePair<SearchResult, bool>();
                }
            }

            return new KeyValuePair<SearchResult,bool>(sr,f);
        }
        
        public KeyValuePair<SearchResult,bool> RemoveSearched(byte[] key, Person person)
        {
            SearchResult old = this.padi.User.Searched.Get(key);
            SearchResult sr;
            Notifica("Comecei a fazer store da chave " + this.padi.User.Searched.GetKey(key));
            if (old == null)
                return new KeyValuePair<SearchResult, bool>(null,false);

            sr = new SearchResult(old);
            bool b = old.Contains(person);

            if (b)
            {
                this.padi.State.IdMensagem.isClient = false;
                IDMsg id;

                lock (this.padi)
                {
                    id = this.padi.State.IdMensagem.Clone();
                }

                this.UpdateReplicas(new RemoveSearchedAction(id, key, person));
                State temp = this.padi.State;

                if (temp.IsOK())
                {
                    sr.Remove(person);
                    old.Remove(person);
                    this.padi.LogFile.Register(new RemoveSearchedAction(id, key, person));
                   // this.padi.State.IdMensagem.Inc();
                    this.padi.Save();
                }
                else
                {
                    lock (this.padi)
                    {
                        this.padi.State.IdMensagem.Dec();
                    }
                    return new KeyValuePair<SearchResult, bool>();
                }
            }
            
            return new KeyValuePair<SearchResult, bool>(sr,b);
        }

        public SearchResult Store(byte [] key, SearchResult sr)
        {
            Notifica("Inicio do store -> Comecei a fazer store da chave " + this.padi.User.Searched.GetKey(sr.Key));
            IDMsg id;
            lock (this.padi)
            {
                id = this.padi.State.IdMensagem.Clone();
                this.padi.State.IdMensagem.Inc();
            }
            id.isClient = false;
            this.UpdateReplicas(new StoreListSearchedAction(id,key,sr));
            State temp = this.padi.State;

            if (temp.IsOK())
            {
                this.padi.User.Searched.Merge(key, sr);
                this.padi.LogFile.Actions.Add(id, new StoreListSearchedAction(id, key, sr));
                //this.padi.State.IdMensagem.Inc();
                this.padi.Save();
                return this.padi.User.Searched.Get(key);
            }
            else
            {
                lock (this.padi)
                {
                    this.padi.State.IdMensagem.Dec();
                }
                return null;
            }
            Notifica("Fim do store");
        }

        public bool HasSearched(byte []key)
        {
            return this.padi.User.Searched.Get(key) != null;
        }
        
        #endregion

        #endregion

        #endregion        
    }
}
